package home.project;

import home.project.misc.Entity;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import haxe.zip.Uncompress;
import home.project.Base64;
import home.project.Main;
import openfl.display.Graphics;
import openfl.display.Tilesheet;
import openfl.display.BitmapData;
import openfl.display.Sprite;
import openfl.geom.Point;
import openfl.geom.Rectangle;
import openfl.geom.Matrix;
import openfl.Assets;

/**
 * ...
 * @author an.vuongngocduy
 */
enum Terrain {
	None;
	Named(n:String);
}

class TerrainInfo {
	@:allow(home.project.misc.Tilemap)
	private static var __names:Array<String> = [""];
	
	public static function toInt(t:Terrain):Int {
		return switch(t) {
			case Terrain.None: -1;
			case Terrain.Named(n): __names.indexOf(n);
		}
	}
	
	public static function fromInt(i:Int):Terrain {
		if (i < 0 || i >= __names.length)
			return Terrain.None;
		return Terrain.Named(__names[i]);
	}
}

class Tileimage
{
	public var source(default, null):String;
	public var width(default, null):Int;
	public var height(default, null):Int;
	public var transparentColor(default, null):Null<Int>;
	
	public function new(p_src:String, ?p_transColor:Int = null)
	{
		var bm:BitmapData = Assets.getBitmapData("img/" + p_src);	
		source = p_src;
		transparentColor = p_transColor;
		width = bm.width;
		height = bm.height;
	}
	
	public function getBitmapData():BitmapData
	{
		var original:BitmapData = Assets.getBitmapData("img/" + source);
		var res:UInt = original.threshold(original, 
				new Rectangle(0, 0, original.width, original.height), new Point(0, 0), 
				"==", transparentColor, 0, 0xFFFFFFFF, true);
		return original;
	}
}

class Tileset
{
	public var name(default, null):String;
	public var firstGid(default, null):Int;
	public var tileWidth(default, null):Int;
	public var tileHeight(default, null):Int;
	public var image(default, set):Tileimage;
	public var tiler(default, null):Tilesheet;
	public var tileTerrains(default, null):Array<Array<Terrain>>;
	
	public function new(p_name:String, p_gid:Int, p_tilew:Int, p_tileh:Int)
	{
		name = p_name;
		firstGid = p_gid;
		tileWidth = p_tilew;
		tileHeight = p_tileh;
		tileTerrains = new Array<Array<Terrain>>();
	}
	
	public function set_image(p_img:Tileimage):Tileimage
	{
		image = p_img;
		tiler = new Tilesheet(image.getBitmapData());
		
		var x:Float = 0;
		var y:Float = 0;
		while(y < image.height)
		{
			x = 0;
			while(x < image.width)
			{
				tiler.addTileRect(new Rectangle(x, y, tileWidth, tileHeight));
				x += tileWidth;
			}
			y += tileHeight;
		}
		return image;
	}
}

class Tilelayer
{
	public var name(default, null):String;
	public var numColumns(default, null):Int;
	public var numRows(default, null):Int;
	public var opacity(default, null):Float;
	public var tiles(default, null):Array<Int>;
	public var displayList(default, null):Array<Array<Float>>;
	public var visible(default, default):Bool;
	
	public function new(p_name:String, p_numTileX:Int, p_numTileY:Int, p_opacity:Float = 1.0)
	{
		name = p_name;
		numColumns = p_numTileX;
		numRows = p_numTileY;
		opacity = p_opacity;
		tiles = new Array<Int>();
		displayList = new Array<Array<Float>>();
	}
}

class Tileblock extends Entity {
	
	public var tileSet(default, null):Int;
	
	public var tileBatch(default,null):Int;
	
	public var terrainLayout(default, null):Array<Int>;
	
	public function new() {
		super();
	}
}

class TileMap extends Entity
{
	private var __version:String;
	private var __tileWidth:Int;
	private var __tileHeight:Int;
	private var __numColumns:Int;
	private var __numRows:Int;
	private var __tileBlocks:Array<Tileblock>;
	private var __tileSets:Array<Tileset>;
	private var __tileLayers:Array<Tilelayer>;
	
	//! constructor
	public function new() {
		super();
	}
	
	//! create map from Tiled map editor
	public static function loadTMX(p_path:String):Tilemap {
		var map:Tilemap = new Tilemap();
		map.__tileSets = new Array<Tileset>();
		map.__tileLayers = new Array<Tilelayer>();
		
		var root:Xml = Xml.parse(Assets.getText(p_path));
		
		var map_node:Xml = root.firstElement();
		map.__version = "tmx:" + map_node.get("version");
		map.__numColumns = Std.parseInt(map_node.get("width"));
		map.__numRows = Std.parseInt(map_node.get("height"));
		map.__tileWidth = Std.parseInt(map_node.get("tilewidth"));
		map.__tileHeight = Std.parseInt(map_node.get("tileheight"));
		
		for (tileset_node in map_node.elementsNamed("tileset"))
		{
			var set:Tileset = new Tileset(tileset_node.get("name"),
									Std.parseInt(tileset_node.get("firstgid")),
									Std.parseInt(tileset_node.get("tilewidth")),
									Std.parseInt(tileset_node.get("tileheight")));
			
			var image_node:Xml = tileset_node.elementsNamed("image").next();
			
			set.image = new Tileimage(image_node.get("source"), 
									image_node.exists("trans") ? Std.parseInt("0xFF" + image_node.get("trans")) : null);
			
			var terraintypes_node:Xml = tileset_node.elementsNamed("terraintypes").next();
			
			if(terraintypes_node != null)
			{
				var begin:Int = TerrainInfo.__names.length;
				for (tr_node in terraintypes_node.elements())
				{
					if (TerrainInfo.__names.indexOf(tr_node.get("name")) != -1)
					{
						TerrainInfo.__names.push(Std.string(set.firstGid) + ":" + tr_node.get("name"));
					}
					else
					{
						TerrainInfo.__names.push(tr_node.get("name"));
					}
				}

				for (tl_node in tileset_node.elementsNamed("tile"))
				{
					var id:Int = Std.parseInt(tl_node.get("id"));
					var tr_arr:Array<String> = tl_node.get("terrain").split(",");
					set.tileTerrains[id] = new Array<Terrain>();
					for (tr_elem in tr_arr)
					{
						var t:Null<Int> = Std.parseInt(tr_elem);
						set.tileTerrains[id].push(t == null ? TerrainInfo.fromInt(-1) : TerrainInfo.fromInt(begin + t));
					}
				}				
			}
			map.__tileSets.push(set);
		}
		
		for (layer_node in map_node.elementsNamed("layer"))
		{
			var layer:Tilelayer = new Tilelayer(layer_node.get("name"),
									Std.parseInt(layer_node.get("width")), Std.parseInt(layer_node.get("height")),
									layer_node.exists("opacity") ? Std.parseFloat(layer_node.get("opacity")) : 1.0);
			
			var data_node = layer_node.elementsNamed("data").next();
			
			if (data_node.exists("encoding"))
			{
				var encoder:String = data_node.get("encoding");
				if (encoder == "base64")
				{
					var decoded:Bytes = Base64.decode(data_node.firstChild().nodeValue);
					if (data_node.exists("compression"))
					{
						var compress:String = data_node.get("compression");
						if (compress == "zlib")
						{
							var stream:BytesInput = new BytesInput(Uncompress.run(decoded));
							stream.bigEndian = false;
							stream.position = 0;
							while (stream.position < stream.length)
							{
								layer.tiles.push(stream.readInt32());								
							}
							stream.close();
						}
					}
				}
				else if (encoder == "csv")
				{
					var cvs:Array<String> = data_node.firstChild().nodeValue.split(",");
					for (id in cvs)
					{
						var gid:Null<Int> = Std.parseInt(id);
						layer.tiles.push(gid == null ? 0 : gid);
					}
				}
			}
			else
			{
				//not yet implemented
			}
			
			for (idx in 0...layer.tiles.length)
			{
				var gid:Int = layer.tiles[idx];
				var inTileset = map.__chooseTileset(gid);
				if (inTileset != -1)
				{			
					var id:Float = gid - map.__tileSets[inTileset].firstGid;
					var x:Float = Std.int(idx % layer.numColumns) * map.__tileWidth;
					var y:Float = (1 + Std.int(idx / layer.numColumns)) * map.__tileHeight - map.__tileSets[inTileset].tileHeight;
											
					if (layer.displayList[inTileset] == null)
						layer.displayList[inTileset] = new Array<Float>();
						
					layer.displayList[inTileset].push(x);
					layer.displayList[inTileset].push(y);
					layer.displayList[inTileset].push(id);
					layer.displayList[inTileset].push(layer.opacity);
				}
			}
			map.__tileLayers.push(layer);
		}
		
		for (objgroup_node in map_node.elementsNamed("objectgroup"))
		{
			//not implemented
		}
			
		for (imglayer_node in map_node.elementsNamed("imagelayer"))
		{
			//not implemented
		}
		
		return map;
	} 
		
	//! load collision map
	public function loadCollisions(p_csv:String):Bool {
		return false;
	}
	
	public function getTerrains(c:Int, r:Int):Iterator<Array<Terrain>> {
		var res:Array<Array<Terrain>> = new Array<Array<Terrain>>();
		for (l in __tileLayers) {
			var tileId = l.tiles[c + r * __numColumns];
			var inTileset:Int = __chooseTileset(tileId);
			res.push(__tileSets[inTileset].tileTerrains[tileId - __tileSets[inTileset].firstGid]);
		}
		return res.iterator();
	}
	
	public function drawLayer(p_g:Graphics, p_idx:Int, p_x:Int, p_y:Int):Void {
		if (p_idx >= __tileLayers.length) return;
		
		if (!__tileLayers[p_idx].visible) return;
		
		for (i in 0...__tileSets.length)
		{
			__tileSets[i].tiler.drawTiles(p_g,__tileLayers[p_idx].displayList[i], false, Tilesheet.TILE_ALPHA);
		}
	}
		 
	public override function draw(p_g:Graphics):Void {
		for (i in 0...__tileLayers.length)
		{
			drawLayer(p_g, i, Std.int(__absoluteTransform.tx), Std.int(__absoluteTransform.ty));
		}
	}	
	
	private inline function __chooseTileset(p_tileId:Int):Int {
		var gid:Int = p_tileId;
		if (gid > 0)
		{
			var inTileset:Int = -1;
			for (i in 0...__tileSets.length)
				inTileset = (gid >= __tileSets[i].firstGid) ? i : inTileset;
			return inTileset;
		}
		return -1;
	}
		
}